/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * VoiceGUI.java
 *
 * Created on Feb 17, 2010, 10:03:33 PM
 */

package VoiceIdent;

import GUI.MainFrame;
import GUI.UserCard;
import edu.cmu.sphinx.frontend.*;
import edu.cmu.sphinx.frontend.frequencywarp.MelFrequencyFilterBank;
import edu.cmu.sphinx.frontend.transform.DiscreteCosineTransform;
import edu.cmu.sphinx.frontend.transform.DiscreteFourierTransform;
import edu.cmu.sphinx.frontend.util.Microphone;
import edu.cmu.sphinx.frontend.window.RaisedCosineWindower;
import edu.cmu.sphinx.util.props.ConfigurationManager;
import edu.cmu.sphinx.util.props.PropertySheet;

import functions.Users;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import model.User;

/**
 *
 * @author JArik
 */

// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

public class VoiceGUI extends javax.swing.JFrame {

    /** Creates new form VoiceGUI */

    private Microphone mic;
    private DiscreteFourierTransform dft;
    private DiscreteCosineTransform iDft;
    private MelFrequencyFilterBank mff;
    private static Object monitor;
    private VoiceData vData;
    private ArrayList<VoiceData> codeBooks;
    private double noiseLevel;
    private VoiceData identData;
    private SwingWorker identWorker;
    private RaisedCosineWindower  rcw;
    private MainFrame mainFr;
    private User activeUser;
    private boolean codebookActive = false;
    
    public VoiceGUI(MainFrame mf) {
        initComponents();
        mainFr = mf;
        this.setTitle("sPitch");
        try {
            codeBooks = VoiceFunctions.loadVoiceData("voiceData");
            System.out.println("CodeBook DeSerialized - "+ codeBooks.size());
        } catch (Exception e)
        {
            System.out.println("New CodeBook");
            codeBooks = new ArrayList<VoiceData>();
            System.out.println(e);
        }


        KeyStroke keyStr = KeyStroke.getKeyStroke(KeyEvent.VK_F1,0);
        InputMap map = winnerTable.getInputMap(javax.swing.JTable.WHEN_IN_FOCUSED_WINDOW);
        map.put(keyStr, "f12");
        ActionMap actionMap = winnerTable.getActionMap();
        actionMap.put("f12", new AbstractAction() {
                     public void actionPerformed(ActionEvent actionEvent)
                     {
                         startRecButtonActionPerformed(null);
                     }
             });
        
    }

    private void initVoice()
    {
        int sampleRate = 8000;
        int sampleSizeInBits = 16;
        int channels = 1;
        boolean signed = true;
        boolean bigEndian = true;
        ConfigurationManager cm = new ConfigurationManager();
        saveButton.setEnabled(false);
        saveOtherButton.setEnabled(false);
        codebookActive = false;

        noiseLevel = Double.parseDouble(noiseLevelBox.getText());
        mic = new Microphone();
        mff = new MelFrequencyFilterBank();
        dft = new DiscreteFourierTransform();
        iDft = new DiscreteCosineTransform();
        rcw = new RaisedCosineWindower();

        // Logging Handler
        cm.addConfigurable(mic, "mic");
        cm.addConfigurable(mff, "mel");
        cm.addConfigurable(iDft, "iDft");
        cm.addConfigurable(rcw, "rcw");

        // configure microphone
        PropertySheet micPS = cm.getPropertySheet("mic");
        
        micPS.setInt(Microphone.PROP_SAMPLE_RATE, sampleRate);
        micPS.setInt(Microphone.PROP_BITS_PER_SAMPLE, sampleSizeInBits);
        micPS.setBoolean(Microphone.PROP_CLOSE_BETWEEN_UTTERANCES, true);
        micPS.setInt(Microphone.PROP_MSEC_PER_READ, 30);
        Logger.getLogger(Microphone.class.getName()).fine("Test");
        
        PropertySheet rcwPS = cm.getPropertySheet("rcw");

        rcwPS.setDouble(RaisedCosineWindower.PROP_WINDOW_SHIFT_MS, 10);
        rcwPS.setDouble(RaisedCosineWindower.PROP_WINDOW_SIZE_MS, 30);


        //configure melFreq
        PropertySheet melPS = cm.getPropertySheet("mel");
        melPS.setDouble(MelFrequencyFilterBank.PROP_MIN_FREQ,400);
        melPS.setDouble(MelFrequencyFilterBank.PROP_MAX_FREQ,3500);
        melPS.setInt(MelFrequencyFilterBank.PROP_NUMBER_FILTERS,32);

        int mfccVectorSize = 13;
        // configure inverse DCT
        PropertySheet iDftPS = cm.getPropertySheet("iDft");
        iDftPS.setInt(DiscreteCosineTransform.PROP_NUMBER_FILTERS, 32);
        iDftPS.setInt(DiscreteCosineTransform.PROP_CEPSTRUM_LENGTH,
                                                             mfccVectorSize+1);



        mic.initialize();

        //rcw.setPredecessor(mic);
        //rcw.initialize();

        // apply DFT to signal from microphone
        dft.setPredecessor(mic);
        dft.initialize();

        // apply Mel Cepstrum transformation to DFT
        mff.setPredecessor(dft);
        mff.initialize();

        // inverse Transformation of Mel - MFCC
        iDft.setPredecessor(mff);
        iDft.initialize();


        // Identification Worker
        identWorker = new SwingWorker()
        {
            @Override
            protected Object doInBackground() throws Exception
            {
               VoiceData identCodeBook;
                    identCodeBook = identData.quantize(16);
               double error = 0;
               int winner = 0;
               ArrayList<IdentCandidate> winnerList =
                                               new ArrayList<IdentCandidate>();
               int maxElement = -1;
               for(int c=0; c< codeBooks.size(); c++)
               {
                    try {
                        error = identCodeBook.estimateError(codeBooks.get(c));
                        maxElement = -1;
                        for(int i=0; i<winnerList.size(); i++)
                            if(winnerList.get(i).getError() > error)
                              maxElement = i;
                        if(winnerList.size() < 5)
                          winnerList.add(new IdentCandidate(error,codeBooks.get(c)));
                        else if(maxElement != -1)
                        {
                            winnerList.remove(maxElement);
                            winnerList.add(new IdentCandidate(error,codeBooks.get(c)));
                        }
                        
                        System.out.println((c + 1) + " = " + error);
                    } catch (Exception ex) {

                        Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
                    }
               }
               DefaultTableModel model = (DefaultTableModel)winnerTable.getModel();
               model.setRowCount(0);
               for(int i=0; i<winnerList.size(); i++)
                   model.addRow(new Object[]{
                                 winnerList.get(i).getVoiceData(),
                                 winnerList.get(i).getError()});
               //winnerTable.getRowSorter().toggleSortOrder(1);
               //winnerTable.getRowSorter().toggleSortOrder(1);
               
               winnerTable.setModel(model);
               startRecButton.setEnabled(true);
               return null;
            }
        };
}
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        startRecButton = new javax.swing.JButton();
        identButton = new javax.swing.JButton();
        statusLabel = new javax.swing.JLabel();
        noiseLevelBox = new javax.swing.JTextField();
        jScrollPane1 = new javax.swing.JScrollPane();
        winnerTable = new javax.swing.JTable();
        closeButton = new javax.swing.JButton();
        saveButton = new javax.swing.JButton();
        closeButton1 = new javax.swing.JButton();
        statusLabel1 = new javax.swing.JLabel();
        stopRecButton = new javax.swing.JButton();
        saveOtherButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });
        getContentPane().setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        startRecButton.setText("Start(F12)");
        startRecButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                startRecButtonActionPerformed(evt);
            }
        });
        getContentPane().add(startRecButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 10, 100, -1));

        identButton.setText("Flush");
        identButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                identButtonActionPerformed(evt);
            }
        });
        getContentPane().add(identButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 280, -1, -1));

        statusLabel.setText("Press start");
        getContentPane().add(statusLabel, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 40, 220, -1));

        noiseLevelBox.setText("1.8");
        getContentPane().add(noiseLevelBox, new org.netbeans.lib.awtextra.AbsoluteConstraints(130, 250, 90, -1));

        winnerTable.setAutoCreateRowSorter(true);
        winnerTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Name", "Error"
            }
        ) {
            Class[] types = new Class [] {
                java.lang.Object.class, java.lang.Double.class
            };
            boolean[] canEdit = new boolean [] {
                false, false
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit [columnIndex];
            }
        });
        winnerTable.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                winnerTableMouseClicked(evt);
            }
        });
        jScrollPane1.setViewportView(winnerTable);
        winnerTable.getColumnModel().getColumn(1).setMaxWidth(70);

        getContentPane().add(jScrollPane1, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 90, 220, 150));

        closeButton.setText("Close");
        closeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButtonActionPerformed(evt);
            }
        });
        getContentPane().add(closeButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(150, 280, 70, -1));

        saveButton.setText("Attach");
        saveButton.setEnabled(false);
        saveButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveButtonActionPerformed(evt);
            }
        });
        getContentPane().add(saveButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 60, 100, -1));

        closeButton1.setText("Remove");
        closeButton1.setMargin(new java.awt.Insets(2, 10, 2, 10));
        closeButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButton1ActionPerformed(evt);
            }
        });
        getContentPane().add(closeButton1, new org.netbeans.lib.awtextra.AbsoluteConstraints(70, 280, 70, -1));

        statusLabel1.setText("NoiseLevel");
        getContentPane().add(statusLabel1, new org.netbeans.lib.awtextra.AbsoluteConstraints(1, 250, 100, -1));

        stopRecButton.setText("Stop");
        stopRecButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopRecButtonActionPerformed(evt);
            }
        });
        getContentPane().add(stopRecButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(110, 10, 110, -1));

        saveOtherButton.setText("Attach to other");
        saveOtherButton.setEnabled(false);
        saveOtherButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveOtherButtonActionPerformed(evt);
            }
        });
        getContentPane().add(saveOtherButton, new org.netbeans.lib.awtextra.AbsoluteConstraints(110, 60, 110, -1));

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void identButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_identButtonActionPerformed
        try {

            statusLabel.setForeground(Color.BLACK);
             if(VoiceFunctions.saveVoiceData(codeBooks))
                 statusLabel.setText("Flushed!");
             else
                 statusLabel.setText("Error!");
        } catch (SQLException ex) {
            Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
        }
        
}//GEN-LAST:event_identButtonActionPerformed

    private void startRecButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startRecButtonActionPerformed

        initVoice();
        vData = new VoiceData(320,0);
        identData = new VoiceData(160,0);
        startRecButton.setEnabled(false);
        ((DefaultTableModel)winnerTable.getModel()).setRowCount(0);
        SwingWorker sw = new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                mic.startRecording();
                statusLabel.setText("Speak");
                statusLabel.setForeground(Color.RED);
                boolean identRan = false;
                while(mic.isRecording())
                {
                    if((iDft.getData() instanceof DoubleData))
                    {
                      System.out.println("Recording - " + vData.getSize() + " " + vData.getPercFilled());
                      DoubleData dData = (DoubleData)iDft.getData();
                      
                      if(dData.getValues()[0] > noiseLevel*1.5)
                      {
                        statusLabel.setText("Speak: "+vData.getPercFilled() + "% " + identData.getPercFilled()+"%");
                        if(!vData.isFilled())
                          vData.push(dData);
                        if(!identData.isFilled())
                          identData.push(dData);
                      }
                      if(identData.isFilled() && !identRan)
                      {
                          identWorker.execute();
                          identRan = true;
                      }

                      if(vData.isFilled())
                          mic.stopRecording();
                      
                    }
                }
                return vData;
            }
            protected void done()
            {
                try {
                    statusLabel.setForeground(Color.GREEN);
                    statusLabel.setText("Done");
                    if(identWorker.isDone())
                        startRecButton.setEnabled(true);

                    codebookActive = true;
                    statusLabel.setText("Done.");
                    saveOtherButton.setEnabled(true);
                    System.gc();
                } catch (Exception ex) {
                    Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
        sw.execute();
    }//GEN-LAST:event_startRecButtonActionPerformed

    private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
        try {
            try {
                VoiceFunctions.saveVoiceData(codeBooks);
                this.dispose();
            } catch (FileNotFoundException ex) {
                
            } catch (IOException ex) {

            }
        } catch (SQLException ex) {
            
        }
    }//GEN-LAST:event_closeButtonActionPerformed

    private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveButtonActionPerformed

        try {
            VoiceData codeBook = vData.quantize(32);
            codeBook.setUserId(activeUser.getId());
            codeBook.setUserName(activeUser.getFullName());
            codeBooks.add(codeBook);
            saveButton.setEnabled(false);
            saveOtherButton.setEnabled(false);
            statusLabel.setText("Saved! - " + activeUser.getId());
            statusLabel.setForeground(Color.BLACK);
        } catch (Exception ex) {

        }
}//GEN-LAST:event_saveButtonActionPerformed

    private void closeButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButton1ActionPerformed
        VoiceData vD = (VoiceData)winnerTable.getValueAt(winnerTable.getSelectedRow(), 0);
        if(codeBooks.remove(vD))
            statusLabel.setText("Removed");
        else
            statusLabel.setText("Failed to remove");
    }//GEN-LAST:event_closeButton1ActionPerformed

    private void stopRecButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopRecButtonActionPerformed
        // TODO add your handling code here:
}//GEN-LAST:event_stopRecButtonActionPerformed

    private void winnerTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_winnerTableMouseClicked
        if(evt.getClickCount() >= 2)
        {
            try {
                VoiceData vD = (VoiceData) winnerTable.getValueAt(winnerTable.getSelectedRow(), 0);
                if(vD.getUserId() == 0)
                    vD.setUserId(1);
                    //throw new Exception("ID Error");
                User user = Users.getById(vD.getUserId());
                System.out.println(user+"");
                UserCard userCard = new UserCard(mainFr, user);
                userCard.setVisible(true);
            } catch (Exception ex) {
                Logger.getLogger(VoiceGUI.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }//GEN-LAST:event_winnerTableMouseClicked

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        SPitch.stop();
    }//GEN-LAST:event_formWindowClosing

    private void saveOtherButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveOtherButtonActionPerformed

}//GEN-LAST:event_saveOtherButtonActionPerformed


    public void setActiveUser(User us)
    {
        if(!saveOtherButton.isEnabled()) return;
        activeUser = us;
        statusLabel.setForeground(Color.GREEN.darker());
        statusLabel.setText("<html>Attach codebook to <b>" + us.getFullName() + "</b> ?");
        saveButton.setEnabled(true);
    }
    
    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        monitor = new Object();
        
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
        }
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                  new VoiceGUI(null).setVisible(true);
            }
        });

        
    }
    public static VoiceGUI run(MainFrame mf)
    {
        VoiceGUI gui=null;
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
        }
       // java.awt.EventQueue.invokeLater(new Runnable() {
           // public void run() {
                  gui = new VoiceGUI(mf);
                  gui.setVisible(true);
       //     }
       // });
        return gui;
    }


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton closeButton;
    private javax.swing.JButton closeButton1;
    private javax.swing.JButton identButton;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextField noiseLevelBox;
    private javax.swing.JButton saveButton;
    private javax.swing.JButton saveOtherButton;
    private javax.swing.JButton startRecButton;
    private javax.swing.JLabel statusLabel;
    private javax.swing.JLabel statusLabel1;
    private javax.swing.JButton stopRecButton;
    private javax.swing.JTable winnerTable;
    // End of variables declaration//GEN-END:variables

}
